/* 
    如何解决递归卡顿
    使用增量渲染（把渲染任务拆分成块，匀到多贞） 将把工作分解成小单元，在完成每个单元之后，如果
    还有其他任务要完成，我们将让浏览器中断渲染，也就是常听到fiber

    1.增量渲染
    2.更新能够暂停，终止，复用渲染任务
    3.给不同类型的更新赋予优先级

    fiber：指组件上将要完成【正在进行的】或者已经完成的任务，每个组件可以有一个或者多个
    每一个fiber节点都有一个React Element
    return:指向父级fiber节点
    child:指向子fiber节点
    sibling:指向兄弟节点
    每个fiber做了三件事：
    1.将元素添加到DOM
    2.为元素的子元素创建fiber
    3.选择下一个工作单元

    requestIdleCallback 这个函数将在浏览器空闲时期被调用

    函数组件和标签组件有两个不同：
    1.函数组件中的fiber没有dom节点
    2.children 是通过函数运行的到而不是props

    函数每调用一次，内部状态就会丢失，所以我们需要一个记录内部状态的变量，设置
    正在工作的fiber和添加一个数组来记录多次调用useState,使用索引来记录
*/

// 下一个功能单元
let nextUniOfWork = null
// 根节点
let wipRoot = null
// 保存根节点更新前的fiber树 添加 alternate 属性到每一个fiber 关联老的 fiber
// 老fiber是我们上一次提交阶段给 DOM 的fiber
let currentRoot = null
// 需要删除的节点
let deletions = null

let wipFiber = null
// 记录当前调用useState的次数
let hookIndex = 0

const isEvent = (key) => key.startsWith('on')
const isProperty = (key) => key !== 'children' && !isEvent(key)
// 是否有新属性
const isNew = (prev, next) => (key) => prev[key] !== next[key]
// 是否是旧属性
const isGone = (prev, next) => (key) => !(key in next)

/**
 * 将虚拟dom转换为真实dom 并添加到真实容器里面
 * @param {*} element 虚拟dom
 * @param {*} container 真实 dom
 */
export function render(element, container) {
	// 根节点
	wipRoot = {
		dom: container,
		props: {
			children: [element],
		},
		alternate: currentRoot,
	}
	deletions = []
	// 设置根节点为下一个工作单元
	nextUniOfWork = wipRoot
}
/** 工作循环
 *
 * @param {*} deadline 截至时间
 */
function workLoop(deadline) {
	// 停止循环标识
	let shouldYield = false
	// 循环条件存在下一个单元，且没有更高优先级的工作
	while (nextUniOfWork && !shouldYield) {
		nextUniOfWork = preformUnitOfWork(nextUniOfWork)
	}
	// 没有下一个工作单元了，这个时候需要把整个 fiber 渲染为dom
	if (!nextUniOfWork && wipRoot) {
		commitRoot()
	}
	// 当前贞空间剩余时间没了，就停止工作
	shouldYield = deadline.timeRemaining() < 1
	// 空闲时间应该任务
	requestIdleCallback(workLoop)
}
/**
 * 协调 diff
 * @param {*} wipFiber
 * @param {*} elements
 */
function reconcileChildren(wipFiber, elements) {
	// 索引
	let index = 0
	// 上一个兄弟节点
	let prevsibling = null
	let oldFiber = wipFiber.alternate && wipFiber.alternate.child
	// 遍历子节点
	while (index < elements.length || oldFiber != null) {
		const element = elements[index]
		// 创建子fiber
		let newFiber = null
		//类型判断
		const sameType = oldFiber && element && element.type === oldFiber.type
		// 类型相同 更新props 加入effectTag 属性用来标识当前执行状态

		if (sameType) {
			// update
			newFiber = {
				type: oldFiber.type,
				props: element.props,
				dom: oldFiber.dom,
				parent: wipFiber,
				alternate: oldFiber,
				effectTag: 'UPDATE',
			}
		}
		// 新的存在并且和老的不同需要新增
		if (element && !sameType) {
			// add
			newFiber = {
				type: element.type,
				props: element.props,
				dom: null,
				parent: wipFiber,
				alternate: null,
				effectTag: 'PLACEMENT',
			}
		}
		// 老的存在并且和新的类型不同需要移除
		// 没有新fiber 将effect 标签添加到旧的fiber中，删除旧的fiber
		if (oldFiber && !sameType) {
			// delete
			oldFiber.effectTag = 'DELETION'
			deletions.push(oldFiber)
		}
		// 处理老fiber的兄弟节点
		if (oldFiber) {
			oldFiber = oldFiber.sibling
		}
		// 将第一个孩子节点设置为fiber的子节点
		if (index === 0) {
			wipFiber.child = newFiber
		} else if (element) {
			// 第一个之外的子节点设置为第一个子节点的兄弟节点
			prevsibling.sibling = newFiber
		}
		prevsibling = newFiber
		index++
	}
}
/**
 * 执行事件单元 ，返回下一个单元事件
 * @param {*} fiber
 */
function preformUnitOfWork(fiber) {
	// 判断是否是函数
	const isFunctionComponent = typeof fiber.type === 'function'
	if (isFunctionComponent) {
		fiber.type.prototype.isReactComponent
			? updateClassComponent(fiber)
			: updateFunctionComponent(fiber)
	} else {
		// 更新普通节点
		updateHostComponent(fiber)
	}
	// 寻找下一个工作单元 先查孩子 然后兄弟 没有就返回父节点
	if (fiber.child) {
		return fiber.child
	}
	let nextFiber = fiber
	while (nextFiber) {
		// 如果有兄弟节点
		if (nextFiber.sibling) {
			return nextFiber.sibling
		}
		//否则返回父节点
		nextFiber = nextFiber.parent
	}
}
/**
 * 处理类组件
 * @param {*} fiber
 */
function updateClassComponent(fiber) {
	const { type, props } = fiber
	const children = [new type(props).render()]
	reconcileChildren(fiber, children)
}
// 处理标签组件
function updateHostComponent(fiber) {
	// 如果没有dom节点就为其创建一个
	if (!fiber.dom) {
		fiber.dom = createDom(fiber)
	}
	// 如果 fiber 有父节点，将 fiber.dom 添加到父节点
	/* 
        问题：
        每处理一个元素，都要向 dom 添加一个新的节点 在完成整个树的渲染之前，由于
        做了可中断操作，那将看到一个不完整的UI,这样显然是不行的
    */
	// if (fiber.parent) {
	// 	fiber.parent.dom.appendChild(fiber.dom)
	// }
	// 获取当前fiber的孩子节点
	const elements = fiber.props.children
	// 协调
	reconcileChildren(fiber, elements)
}
/**
 * 处理函数组件
 * @param {*} fiber
 */
function updateFunctionComponent(fiber) {
	wipFiber = fiber
	hookIndex = 0
	wipFiber.hooks = []
	const children = [fiber.type(fiber.props)]
	reconcileChildren(fiber, children)
}
/**
 * 创建dom
 * @param {*} fiber
 */
function createDom(fiber) {
	const dom =
		fiber.type === 'TEXT_ELEMENT'
			? document.createTextNode('')
			: document.createElement(fiber.type)
	// 为节点绑定属性

	updateDom(dom, {}, fiber.props)
	return dom
}
/**
 * 处理提交的fiber树
 * @param {*} fiber
 */
function commitWork(fiber) {
	if (!fiber) return
	// 处理没有dom的fiber节点
	let parentFiberNode = fiber.parent
	// 处理函数组件 一直向上找直到找到没有 dom 节点
	while (!parentFiberNode.dom) {
		parentFiberNode = parentFiberNode.parent
	}
	const parentNode = parentFiberNode.dom
	// 处理 effectTag 标记 处理新增节点（PLACEMENT）
	if (fiber.effectTag === 'PLACEMENT' && fiber.dom) {
		// 将自己添加到父节点下
		parentNode.appendChild(fiber.dom)
	} else if (fiber.effectTag === 'DELETION') {
		// 处理删除节点标记
		commitDeletion(fiber, parentNode)
	} else if (fiber.effectTag === 'UPDATE' && fiber.dom) {
		updateDom(fiber.dom, fiber.alternate.props, fiber.props)
	}
	// 渲染子节点
	commitWork(fiber.child)
	// 渲染兄弟节点
	commitWork(fiber.sibling)
}
/**
 * 提交任务，将fiber tree 渲染为真实 dom
 */
function commitRoot() {
	// 遍历删除旧节点
	deletions.forEach(commitWork)
	commitWork(wipRoot.child)
	currentRoot = wipRoot
	wipRoot = null
}
/**
 * 更新 dom 属性
 * @param {*} dom
 * @param {*} prevProps
 * @param {*} nextProps
 */
function updateDom(dom, prevProps, nextProps) {
	// 移除老的事件监听
	Object.keys(prevProps)
		.filter(isEvent)
		.filter(
			(key) =>
				isGone(prevProps, nextProps)(key) ||
				isNew(prevProps, nextProps)(key)
		)
		.forEach((name) => {
			const eventType = name.toLowerCase().substring(2)
			dom.removeEventListener(eventType, prevProps[name])
		})
	// 添加新的事件监听
	Object.keys(nextProps)
		.filter(isEvent)
		.filter(isNew(prevProps, nextProps))
		.forEach((name) => {
			const eventType = name.toLowerCase().substring(2)
			dom.addEventListener(eventType, nextProps[name])
		})
	// 移除老的属性
	Object.keys(prevProps)
		.filter(isProperty)
		.filter(isGone(prevProps, nextProps))
		.forEach((name) => {
			dom[name] = ''
		})
	// 设置新的属性
	Object.keys(nextProps)
		.filter(isProperty)
		.filter(isNew(prevProps, nextProps))
		.forEach((name) => {
			dom[name] = nextProps[name]
		})
}
/**
 * 删除情况下，不断向下找，直到找到有dom的子节点
 * @param {*} fiber
 * @param {*} parentNode
 */
function commitDeletion(fiber, parentNode) {
	if (fiber.dom) {
		parentNode.removeChild(fiber.dom)
	} else {
		// 处理函数组件
		commitDeletion(fiber.child, parentNode)
	}
}
/**
 *
 * @param {*} initial 初始值
 */
export function useState(initial) {
	// 检查是否有旧的hooks
	const oldHook =
		wipFiber.alternate &&
		wipFiber.alternate.hooks &&
		wipFiber.alternate.hooks[hookIndex]

	// 如果有旧的，就复制到新的 没有就初始化
	const hook = {
		state: oldHook ? oldHook.state : initial,
		queue: [],
	}
	const actions = oldHook ? oldHook.queue : []
	actions.forEach((action) => {
		hook.state = typeof action === 'function' ? action(hook.state) : action
	})

	// console.log('hook.state :>>', hook.state)
	// 设置hook状态
	const setState = (action) => {
		hook.queue.push(action)
		// 设置一个新的正在进行的工作为下一个工作单元，这样工作循环就可以开始一个新的渲染阶段
		wipRoot = {
			dom: currentRoot.dom,
			props: currentRoot.props,
			alternate: currentRoot,
		}
		nextUniOfWork = wipRoot
		deletions = []
	}
	wipFiber.hooks.push(hook)
	hookIndex++
	return [hook.state, setState]
}
// 空闲时间应该任务
requestIdleCallback(workLoop)
